package view;

import game.Game;
import game.MoveExecutionList;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;

import parser.ParserErrorConstants;
import parser.XMLParserAndReader;
import chess.Board;
import chess.Player;
import chess.Tile;

public class GraphicalView  implements View{

	//Chess Board Colors
	public static final Color LIGHT_BROWN_COLOR = new Color(139,69,19);
	public static final Color DARK_BROWN_COLOR = new Color(245,222,179);
	public static final Color OLIVE_GREEN_COLOR = new Color(85,107,47);
	public static final Color BUFF_COLOR = new Color(238,232,170);
	public static final Color LIGHT_BLUE_COLOR = new Color(135,206,250);
	public static final Color DARK_BLUE_COLOR = new Color(0,0,205);
	
	

	public static final int PLAYER_1 = 0;
	public static final int PLAYER_2 = 1;
	
	protected Board board;
	
	//These specify the current tile being selected in this view
	protected int selectedX; //The value of -1 will specify an invalid selection
	protected int selectedY;
	
	protected Color color1;
	protected Color color2;
	//Using chessbuttons, which store a coordinate in them.
	protected ArrayList<ArrayList<ChessButton>> grid;
	
	protected JFrame mainWindow;
	protected JMenuBar chessMenuBar;
	private Container gridContainer;
	
	boolean newGameSelected;
	protected ChessImageLibrary images;
	
	public GraphicalView(Board b) {
		super();
		newGameSelected = false;
		board = b;
		color1=LIGHT_BROWN_COLOR;
		color2=DARK_BROWN_COLOR;
		//Prepare the image Library
		images=new ChessImageLibrary();
		ActionListener chessController = new ActionListener(){

			@Override
			public void actionPerformed(ActionEvent arg0) {
				ChessButton sourceButton = (ChessButton)arg0.getSource();
				selectedX = sourceButton.getX();
				selectedY = sourceButton.getY();
				
			}
			
		};
		
		grid = new ArrayList<ArrayList<ChessButton>>();
		gridContainer=new Container();
		
		selectedX=-1;
		selectedY=-1;
		
		mainWindow=new JFrame("Game");
		createMenu();


		
		mainWindow.setSize(800,800); //This gives us nice, large 100X100 tiles
		mainWindow.setResizable(false); //We don't want the user messing up our nice square tiles
		mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Close java when you close the window
		
		mainWindow.getContentPane().add(gridContainer, BorderLayout.CENTER);
		//Set up the window as a grid
		gridContainer.setLayout(new GridLayout(Board.BOARD_DIMENSION,Board.BOARD_DIMENSION));
		
		Color gridColour = color1;
		ChessButton newButton;
		for (int curY=0;curY<Board.BOARD_DIMENSION;curY++){
			grid.add(new ArrayList<ChessButton>());  //Add a new row
			
			//Get the next colour to start the grid
			if (gridColour==color2){
				gridColour=color1;
			}
			else{
				gridColour=color2;
			}
			
			for (int curX=0;curX<Board.BOARD_DIMENSION;curX++){
				
				//prepare the next button to be added
				newButton=new ChessButton(curX,curY,gridColour); //Create the next button to be added
				newButton.addActionListener(chessController);
				newButton.getButton().setBackground(gridColour);
				
				//Make the text show up nicely
				if (newButton.getColor()==color1){
					newButton.getButton().setForeground(color2);
				}
				else{
					newButton.getButton().setForeground(color1);
				}
				
				//Add the button to the grid and the display
				grid.get(curY).add(newButton);
				gridContainer.add(newButton.getButton());
				
				//Change to the next colour
				if (gridColour==color2){
					gridColour=color1;
				}
				else{
					gridColour= color2;
				}
			}
		}
		
		
		mainWindow.setAlwaysOnTop(true);
	
		
		mainWindow.setVisible(true);
		mainWindow.setAlwaysOnTop(false); //Doing this (forcing it to always be on top,
										  //then telling it not to always be on top), pops
		                                  //the window to the front, but then allows you to 
										  //take the focus off of it if you want.
		mainWindow.repaint();
	}

	@Override
	public Tile chooseTile() {
		clearSelectedTile(); //First set to no tile selected
		while ((selectedX==-1)||(selectedY==-1)){
			if(newGameSelected){
				return null;
			}
		}
		return board.getTile(selectedX, selectedY); //Return the selected tile
	}

	/**
	 * The clearSelectedTile method resets the "selected tile" x and y coordinates
	 * to an invalid value, so that the game can determine when the user has actually
	 * selected a tile.
	 * 
	 * <pre>
	 * Max Joyce
	 * -Created (Feb 12, 2010)
	 * </pre>
	 */
	private void clearSelectedTile() {
		this.selectedX=-1;
		this.selectedY=-1;
	}

	/**
	 * This method when called will close the view
	 */
	public void closeView() {
		if(mainWindow != null){
			mainWindow.dispose();

		}
	}

	public void refreshTileColors() {
		Color gridColour = color1;
		ChessButton button;
		for (int curY=0;curY<Board.BOARD_DIMENSION;curY++){
		
			//Get the next colour to start the grid
			if (gridColour==color2){
				gridColour=color1;
			}
			else{
				gridColour=color2;
			}
			
			for (int curX=0;curX<Board.BOARD_DIMENSION;curX++){
				button=grid.get(curX).get(curY);
				button.setColor(gridColour);
				button.getButton().setBackground(gridColour);
				
				//Make the text show up nicely
				if (button.getColor()==color1){
					button.getButton().setForeground(color2);
				}
				else{
					button.getButton().setForeground(color1);
				}
	
				
				//Change to the next colour
				if (gridColour==color2){
					gridColour=color1;
				}
				else{
					gridColour= color2;
				}
			}
		}
	}
	/**
	 * This method will print out the current error code in the text box. It will 
	 * also print out an error code on the debug console
	 * @param errorCode The error code that has been flaged
	 */
	protected void handleImportErrorCode(int errorCode) {
		if(errorCode == ParserErrorConstants.FILE_IO_ERROR){
			showOutputMessage("File I/O Error");
		}else if(errorCode == ParserErrorConstants.FILE_NOT_FOUND_ERROR){
			showOutputMessage("File Not Found");
		}else if(errorCode == ParserErrorConstants.FILE_UNABLE_TO_CLOSE){
			showOutputMessage("File Unable to Close");
		}else if(errorCode == ParserErrorConstants.FILE_UNABLE_TO_OPEN){
			showOutputMessage("File Unable to Open");
		}else if(errorCode == ParserErrorConstants.INVALID_GAME_TYPE_ERROR){
			showOutputMessage("Cannot Open a File of a Different Game");
		}else if(errorCode == ParserErrorConstants.UNABLE_TO_CREATE_STRING_FILE_ERROR){
			showOutputMessage("Cannot Create String for Parsing");
		}else if(errorCode >= ParserErrorConstants.FILE_UNABLE_TO_READ){
			showOutputMessage("File is Corrupted");
		}
		printlnToDebugConsole("Error Code is : " + errorCode);
	}

	/**
	 * This will create the menu and all its behavioral properties
	 */
	protected void createMenu() {
		chessMenuBar = new JMenuBar();
		mainWindow.setJMenuBar(chessMenuBar);
		JMenu fileMenu = new JMenu("File");
		JMenu gameMenu = new JMenu("Game");  //houses all the state affecting options save,load,etc...
		JMenu iconMenu = new JMenu("Icons"); //Lets you pick your icon set
		JMenu boardMenu = new JMenu ("Board"); //Let's you pick the board colours
		JMenu helpMenu = new JMenu("Help");
	
		
		chessMenuBar.add(fileMenu);
		chessMenuBar.add(gameMenu);
		chessMenuBar.add(iconMenu);
		chessMenuBar.add(boardMenu);
		chessMenuBar.add(helpMenu);
		
		
		JMenuItem undoOption = new JMenuItem("Undo");
		JMenuItem redoOption = new JMenuItem("Redo");
		
		JMenuItem saveOption = new JMenuItem("Save");
		JMenuItem loadOption = new JMenuItem("Load");
		
		//These can be permanently disabled until we add the functionality
		
		JMenuItem iconSet1Option = new JMenuItem("Set 1 (2D)");
		JMenuItem iconSet2Option = new JMenuItem("Set 2 (Isometric)");
		JMenuItem iconSet3Option = new JMenuItem("Set 3 (Real)");
		JMenuItem iconSet4Option = new JMenuItem("Set 4 (2D)");
		
		JMenuItem boardBWOption = new JMenuItem("Black and White");
		JMenuItem boardBrownsOption = new JMenuItem("Browns");
		JMenuItem boardGreenBuffOption = new JMenuItem("Green & Buff");
		JMenuItem boardBluesOption = new JMenuItem("Blues");
		
		JMenuItem exitOption = new JMenuItem("Exit");
		JMenuItem newGameOption = new JMenuItem("New Game");
		JMenuItem helpOption = new JMenuItem("Help");
		JMenuItem aboutOption = new JMenuItem("About");
		
		saveOption.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,InputEvent.CTRL_MASK));
		saveOption.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0){
				Game game = board.getGame();
				if(game.getMoveExecutionList().exportList().length() != 0){
					XMLParserAndReader importList = new XMLParserAndReader(board,game.getPlayers());
					importList.setUpSaveChooser(mainWindow);
					if(importList.writeFile()){
						showOutputMessage("File Saved");
					}else {
						showOutputMessage("File Was Not Saved");
					}
				}else {
					showOutputMessage("Please Make a Move Before Saving");
				}
			}
		});
		loadOption.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,InputEvent.CTRL_MASK));
		loadOption.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0){
				Game game = board.getGame();
				XMLParserAndReader parser = new XMLParserAndReader(board,game.getPlayers());
				parser.setUpLoadChooser(mainWindow);
				if(parser.isAbleToParse()){
					game.resetBoard();
					MoveExecutionList commands = parser.parseFile();
					if(commands != null){
						//System.out.println("Commands :\n" + commands.toString());
						game.setMoveList(commands);
						game.setPlayer(parser.getCurrentPlayer());
						showOutputMessage("Import Done");
						displayBoard();
					}else{
						handleImportErrorCode(parser.getErrorCode());
						game.restoreBoard();
						//displayBoard();
					}
				}else{
					handleImportErrorCode(parser.getErrorCode());
				}
			}
		});		
		// Redo Option
		redoOption.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y,InputEvent.CTRL_MASK));
		redoOption.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0){
				/*game.Game game = board.getGame();
				if(game instanceof game.ChessGame){
					((game.ChessGame)game).redo();
				}
				else if (game instanceof game.KnightsTourGame){
					((game.KnightsTourGame)game).redo();
				}*/
				board.getGame().redo();
				//displayBoard();
			}
		});
		// Undo Option
		undoOption.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z,InputEvent.CTRL_MASK));
		undoOption.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0){
				/*game.Game game = board.getGame();
				if(game instanceof game.ChessGame){
					((game.ChessGame)game).undo();
				}
				else if (game instanceof game.KnightsTourGame){
					((game.KnightsTourGame)game).undo();
				}*/
				board.getGame().undo();
				//displayBoard();
			}
		});
		// NEW GAME
		newGameOption.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2,0));
		newGameOption.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0){
				newGameSelected = true;
				board.getGame().newGameSelected();
			}
		});
		
		// ABOUT OPTION
		aboutOption.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0){
				JOptionPane.showMessageDialog(mainWindow, aboutMessage());
			}
			private String aboutMessage(){
				String message = "";
				message += "\tChess Game \n";
				message += "This game was created by : \n";
				message += "Max Joyce, Andrew Jeffery, Caleb Chan, Samual Martelli\n";
				return message;
			}
		});
		// EXIT OPTION
		exitOption.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q,InputEvent.CTRL_MASK));
		exitOption.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent arg0){
				//System.out.println(board.getGame().listOfMoves.exportList());
				System.exit(0);
			}
		});
		// HELP OPTION
		helpOption.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1,0));
		helpOption.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent arg0){
				JOptionPane.showMessageDialog(mainWindow, helpMessage(board.getGame()));
			}
			private String helpMessage(game.Game game){
				if(game instanceof game.ChessGame){
					return chessHelpMessage();
				}else if(game instanceof game.EightQueensGame){
					return queensHelpMessage();
				}else if(game instanceof game.KnightsTourGame){
					return knightsHelpMessage();
				}else{
					return "INVALID GAME";
				}
			}
			private String chessHelpMessage(){
				String message = "";
				message += "The object of the game is to place the king in checkmate\n";
				message += " The more pieces you take the better the chances are of winning.\n";
				message += "The game ends when the king cannot move and is the only piece(stalemate).";
				message += "Or the king is being attacked and cannot get out of attack(checkmate)";
				return message;
			}
			private String queensHelpMessage(){
				String message = "";
				message += "The goal of the game is to place 8 queens on the board\n";
				message += "with out confict. Select an empty tile to place a queen.\n";
				message += "Select an occupied tile to remove a queen.";
				return message;
			}
			private String knightsHelpMessage(){
				String message = "";
				message += "The goal of the game is to take a knight and try to move to every\n";
				message += "square without going over a square twice. Place a knight by clicking\n";
				message += "on an empty square. Onced placed click on a highligited square to\n";
				message += "move it to a valid location.\n";
				return message;
			}
		});
	
		
		//Icon Options
		iconSet1Option.addActionListener(new ActionListener(){
	
			@Override
			public void actionPerformed(ActionEvent arg0) {
				images.setDirectory("images/set1/");
				images.reloadImages();
				displayBoard();
			}
		});
		iconSet2Option.addActionListener(new ActionListener(){
	
			@Override
			public void actionPerformed(ActionEvent arg0) {
				images.setDirectory("images/set2/");
				images.reloadImages();
				displayBoard();
			}
		});
		iconSet3Option.addActionListener(new ActionListener(){
	
			@Override
			public void actionPerformed(ActionEvent arg0) {
				images.setDirectory("images/set3/");
				images.reloadImages();
				displayBoard();
			}
		});
		
		iconSet4Option.addActionListener(new ActionListener(){
	
			@Override
			public void actionPerformed(ActionEvent arg0) {
				images.setDirectory("images/set4/");
				images.reloadImages();
				displayBoard();
			}
		});
		
		boardBWOption.addActionListener(new ActionListener(){
	
			@Override
			public void actionPerformed(ActionEvent arg0) {
				//Set Color 1
				color1=Color.black;
				//Set Color 2
				color2=Color.white;
				
				refreshTileColors();
				displayBoard();
			}
		});	
	
		boardBrownsOption.addActionListener(new ActionListener(){
	
			@Override
			public void actionPerformed(ActionEvent arg0) {
				//Set Color 1
				color1=LIGHT_BROWN_COLOR;
				//Set Color 2
				color2=DARK_BROWN_COLOR;
				
				refreshTileColors();
				displayBoard();
			}
		});
		
		boardGreenBuffOption.addActionListener(new ActionListener(){
	
			@Override
			public void actionPerformed(ActionEvent arg0) {
				//Set Color 1
				color1=BUFF_COLOR;
				//Set Color 2
				color2=OLIVE_GREEN_COLOR;
				
				refreshTileColors();
				displayBoard();
			}
		});
	
		boardBluesOption.addActionListener(new ActionListener(){
	
			@Override
			public void actionPerformed(ActionEvent arg0) {
				//Set Color 1
				color1=LIGHT_BLUE_COLOR;
				//Set Color 2
				color2=DARK_BLUE_COLOR;
				
				refreshTileColors();
				displayBoard();
			}
		});
		
		fileMenu.add(newGameOption);
		fileMenu.addSeparator();
		fileMenu.add(saveOption);
		fileMenu.add(loadOption);
		fileMenu.addSeparator();
		fileMenu.add(exitOption);
		
		gameMenu.add(undoOption);
		gameMenu.add(redoOption);
		
		iconMenu.add(iconSet1Option);
		iconMenu.add(iconSet2Option);
		iconMenu.add(iconSet3Option);
		iconMenu.add(iconSet4Option);
		
		boardMenu.add(boardBWOption);
		boardMenu.add(boardBrownsOption);
		boardMenu.add(boardGreenBuffOption);
		boardMenu.add(boardBluesOption);
		
		helpMenu.add(helpOption);
		helpMenu.add(aboutOption);
	}

	@Override
	public void displayBoard() {
		ChessButton curButton;
		Tile curTile;
		for (int curX=0;curX<Board.BOARD_DIMENSION;curX++){
			for (int curY=0;curY<Board.BOARD_DIMENSION;curY++){
				curButton = getGridButton(curX,curY);
				curButton.getButton().setBackground(curButton.getColor()); //Reset it back to default colour
				curTile = board.getTile(curX, curY);
				if (curTile.isPieceHere()){
					//curButton.getButton().setText(curTile.getPieceHere().toString()); //Put the string rep on the button
					//Set the picture on the button
					images.setButtonImage(curButton, board);
				}
				else{
					images.clearTile(curButton);
				}
			}
		}
		
	}
	/**
	 * The getButtonFromTile method is used to grab the corresponding button
	 * <pre>
	 * Max Joyce
	 * -Creation (Feb 18, 2010)
	 * </pre>
	 */
	private ChessButton getButtonFromTile(chess.Tile tile) {
		return getGridButton(tile.getX(),tile.getY());
	}

	/**
	 * The getGridButton method can be used to grab a button of of the grid, and will be used
	 * in particular when trying to update the board display.
	 * <pre>
	 * Max Joyce
	 * -Creation(Feb 15,2010)
	 * </pre>
	 * 
	 * @param x the x coordinate of the button
	 * @param y the y coordinate of the button
	 * @return the ChessButton at the provided coordinates
	 */
	private ChessButton getGridButton(int x, int y) {
		return grid.get(y).get(x);
	}

	/**
	 * This method allows the game to ask the user for a selection.
	 * <pre>
	 * Max Joyce
	 * -Creation (Feb 18,2010)
	 * </pre>
	 * 
	 */
	public String getUserChoice(Object[] options, String question) {
		Object selection;
		while(true){
			selection=JOptionPane.showInputDialog(null,question,"Question",JOptionPane.QUESTION_MESSAGE,null,options,options[0]);
			for (Object o:options){
				if (selection.equals(o)){
					return (String)selection;
				}
			}
		}
	}


	/**
	 * This method will output the message as a pop up window 
	 * @param message The message to be output
	 */
	public void showOutputMessage(String message) {
		JOptionPane.showMessageDialog(mainWindow, message);
	}

	/**
	 * In a graphical view, the showPossibleMoves method will highlight tiles that can be moved
	 * to or attacked
	 * <pre>
	 * Max Joyce
	 * -Created (Feb 18, 2010)
	 * </pre>
	 */
	public void showPossibleMoves(ArrayList<chess.Tile> mTiles, ArrayList<chess.Tile> aTiles, chess.Tile curTile) {
		ChessButton button;
		//Moves
		for(Tile tile:mTiles){
			button=getButtonFromTile(tile);
			button.getButton().setBackground(Color.yellow);
		}
		
		//Attacks
		for(Tile tile:aTiles){
			button=getButtonFromTile(tile);
			button.getButton().setBackground(Color.red);
		}
		
		button=getButtonFromTile(curTile);
		button.getButton().setBackground(Color.green);
		
		//System.out.println("");
	}

	/**
	 * This method will show all possible moves that a piece can make on the board.
	 * @param mTiles The movement tiles that the piece can move
	 * @param cuTile The current tile that the piece is located
	 */
	public void showPossibleMoves(ArrayList<chess.Tile> mTiles, chess.Tile curTile) {
		ChessButton button;
		//Moves
		for(Tile tile:mTiles){
			button=getButtonFromTile(tile);
			button.getButton().setBackground(Color.yellow);
			//System.out.println("Yellowed");
		}
		
		button=getButtonFromTile(curTile);
		button.getButton().setBackground(Color.green);
		
		//System.out.println("");
	}

	@Override
	public void clearDebugConsole() {	
	}

	@Override
	public String getDebugConsoleText() {
		return null;
	}

	@Override
	public void printlnToDebugConsole(String message) {
	}

	@Override
	public void setDebugText(String message) {
	}

	@Override
	public void updateCurrentPlayerTurn(Player currentPlayer) {
	}
}